<html>
<head>
<script>

var logDiv;

function log(msg, success)
{
    logDiv.appendChild(document.createElement('div')).textContent = msg + ': ' + (success ? 'PASS' : 'FAIL');
}

function moveOver(element)
{
    if (!window.eventSender)
        return;

    var x = element.offsetLeft + element.offsetWidth / 2;
    var y = element.offsetTop + element.offsetHeight / 2;
    eventSender.mouseMoveTo(x, y);
}

function moveOverLeftQuarterOf(element)
{
    if (!window.eventSender)
        return;

    var x = element.offsetLeft + element.offsetWidth / 4;
    var y = element.offsetTop + element.offsetHeight / 2;
    eventSender.mouseMoveTo(x, y);
}

function moveOverRightQuarterOf(element)
{
    if (!window.eventSender)
        return;

    var x = element.offsetLeft + element.offsetWidth * 3 / 4;
    var y = element.offsetTop + element.offsetHeight / 2;
    eventSender.mouseMoveTo(x, y);
}

function clickOn(element)
{
    moveOver(element);
    eventSender.mouseDown();
    eventSender.mouseUp();
}

function clickOnLeftQuarterOf(element)
{
    if (!window.eventSender)
        return;

    moveOverLeftQuarterOf(element);
    eventSender.mouseDown();
    eventSender.mouseUp();
}

function leapForward()
{
    if (!window.eventSender)
        return;

    eventSender.leapForward(1000);
}

var tests = {
    mutationEventPropagation: function()
    {
        var textarea = document.body.appendChild(document.createElement('textarea'));
        var mutationEventFired;
        textarea.addEventListener('DOMSubtreeModified', function(e)
        {
            mutationEventFired = true;
        }, false);
        textarea.value = 'test';
        // Trigger style recalc and sadly, the actual mutation of the textarea shadow DOM.
        textarea.offsetHeight;
        log('Mutation events should not propagate out of the shadow DOM', !mutationEventFired);
        textarea.parentNode.removeChild(textarea);
    },
    selectstartEventPropagation: function()
    {
        var textInput = document.body.appendChild(document.createElement('input'));
        var selectstartEventFired = false;
        document.selectstart = function()
        {
            selectstartEventFired = true;
        }
        clickOn(textInput);
        log('The selectstart event should not propagate out of the shadow DOM', !selectstartEventFired);
        textInput.parentNode.removeChild(textInput);
        document.selectstart = null;
    },
    mouseOverAndOutPropagation: function()
    {
        var count = 0;
        var fileInput = document.body.appendChild(document.createElement('input'));
        fileInput.setAttribute('type', 'file');
        var countEventDispatch = function()
        {
            count++;
        }
        moveOverLeftQuarterOf(fileInput);

        document.body.addEventListener('mouseover', countEventDispatch, false);
        document.body.addEventListener('mouseout', countEventDispatch, false);

        moveOverRightQuarterOf(fileInput);

        log("The mouseover/mouseout event between two elements inside the same shadow subtree should not propagate out of the shadow DOM", count == 0);

        document.body.removeEventListener('mouseover', countEventDispatch, false);
        document.body.removeEventListener('mouseout', countEventDispatch, false);
        fileInput.parentNode.removeChild(fileInput);
    },
    relatedTargetAsHost: function()
    {
        var count = 0;
        var relatedTarget = document.createElement('div');
        relatedTarget.style.cssText = 'width: 50px; height: 50px; padding-left: 50px;';
        document.body.appendChild(relatedTarget);
        var target = document.createElement('div');
        target.style.cssText = 'width: 50px; height: 50px';
        relatedTarget.attachShadow({mode: 'open'}).appendChild(target);
        moveOverLeftQuarterOf(relatedTarget);
        var countEventDispatch = function()
        {
            count++;
        }
        relatedTarget.addEventListener('mouseover', countEventDispatch, false)
        moveOverRightQuarterOf(relatedTarget);

        log("The mouseover event in a shadow subtree, where related target is the tree host should not escape out of shadow DOM", count == 0);

        relatedTarget.removeEventListener('mouseover', countEventDispatch, false);
        document.body.removeChild(relatedTarget);
    },
    targetAsHost: function()
    {
        var count = 0;
        var target = document.createElement('div');
        target.style.cssText = 'width: 50px; height: 50px; padding-left: 50px;';
        document.body.appendChild(target);
        var relatedTarget = document.createElement('div');
        relatedTarget.style.cssText = 'width: 50px; height: 50px';
        target.attachShadow({mode: 'open'}).appendChild(relatedTarget);
        moveOverRightQuarterOf(target);
        var countEventDispatch = function(evt)
        {
            count++;
        }
        target.addEventListener('mouseover', countEventDispatch, false)
        moveOverLeftQuarterOf(target);

        log("Events with relatedTarget should not escape out of shadow subtree when its host is the target", count == 0);

        target.removeEventListener('mouseout', countEventDispatch, false);
        document.body.removeChild(target);
    },
    mouseOverOnHost: function()
    {
        var count = 0;
        var input = document.body.appendChild(document.createElement('input'));
        var countEventDispatch = function()
        {
            count++;
        }

        moveOver(document.body);
        input.addEventListener('mouseover', countEventDispatch, false);
        moveOver(input);

        log("The mouseover/mouseout event on a shadow subtree host should propagate out of the shadow DOM", count == 1);

        document.body.removeEventListener('mouseover', countEventDispatch, false);
        input.parentNode.removeChild(input);
    },
    labelSyntheticClick: function()
    {
        var count = 0;
        var label = document.body.appendChild(document.createElement('label'));
        var searchInput = label.appendChild(document.createElement('input'));
        searchInput.setAttribute('type', 'search');
        searchInput.setAttribute('id', 'baz');
        label.setAttribute('for', 'baz');
        searchInput.addEventListener('click', function(e)
        {
            count++;
        }, false);
        clickOn(searchInput);
        log("Label should look beyond shadow boundary to detect if it encloses its associated element", count == 1);
        label.parentNode.removeChild(label);
    },
    /* This subtest started crashing after r89007:
     *   https://bugs.webkit.org/show_bug.cgi?id=62788
     * I'm disabling this test for now while I ask for help understanding the problem.
    defaultEventRetargeting: function()
    {
        var count = 0;
        var fileInput = document.body.appendChild(document.createElement('input'));
        fileInput.setAttribute('type', 'file');
        var counter = function()
        {
            count++;
        }
        document.body.addEventListener('DOMActivate', counter, false);
        clickOnLeftQuarterOf(fileInput);
        log("Events for default event handler should not be retargeted", count == 1);
        document.body.removeEventListener('DOMActivate', counter, false);
        fileInput.parentNode.removeChild(fileInput);
    },
    */
    relatedTargetRetargeting: function()
    {
        var count = 0;
        var textInput = document.body.appendChild(document.createElement('input'));
        var counter = function(evt)
        {
            if (evt.relatedTarget && !evt.relatedTarget.parentNode)
                count++;
        }
        moveOver(textInput);
        document.body.addEventListener("mouseover", counter, false);
        moveOver(document.body);
        document.body.removeEventListener("mouseover", counter, false);
        log("Event's relatedTarget should be retargeted", count == 0);
        textInput.parentNode.removeChild(textInput);
    },
    eventInProgress: function()
    {
        var textInput = document.body.appendChild(document.createElement('input'));
        textInput.addEventListener('click', function(e)
        {
            log('Other events should be retargeted', e.target == textInput);
        }, false);
        clickOn(textInput);
        textInput.parentNode.removeChild(textInput);
    },
    finalEventObject: function()
    {
        var textInput = document.body.appendChild(document.createElement('input'));
        var storedEvent;
        textInput.addEventListener('click', function(e)
        {
            storedEvent = e;
        }, false);
        clickOn(textInput);
        log('After event dispatch, the event object should not reveal shadow DOM', storedEvent && storedEvent.target == textInput);
        textInput.parentNode.removeChild(textInput);
    },
    focusEventPropagation: function()
    {
        var searchInput = document.body.appendChild(document.createElement('input'));
        searchInput.setAttribute('type', 'search');
        var count = 0;
        searchInput.addEventListener('focus', function(evt)
        {
            count++;
        });
        clickOn(searchInput);
        leapForward();
        clickOn(searchInput);
        log('Focusing same shadow DOM element repeatedly should not trigger multiple focus/blur events', count == 1);
        searchInput.parentNode.removeChild(searchInput);
    }
};

function runTest()
{
    if (window.testRunner)
        testRunner.dumpAsText();

    logDiv = document.getElementById('log');
    for(var testName in tests) {
        tests[testName]();
    }
}

</script>
</head>
<body onload="runTest()">
    <p>Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.
    <p>See <a href="https://bugs.webkit.org/show_bug.cgi?id=46015">bug 46015</a> for details.
    <div id="log"></div>
</body>
</html>
